﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.IO;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

//
//
// Good reference for MIDI spec 1.1:    http://sander.vanzoest.com/talks/2002/audio_and_apache/midispec.html /////////
// also some info at:   http://www.skytopia.com/project/articles/midi.html    //////////
// THANK YOU !!!!!
// 
// MIDI files should be TYPE 0, which is all instuments in one track,  Type 1 is multiple tracks END-to-END //
// Midi type converter, such as: http://web.archive.org/web/20071011001724/http://www.borg.com/~jglatt/progs/software.htm //

namespace NetduinoMidiPlayer
{
    public class Program
    {
        public static void Main()
        {
            // Set up song thread
            var midiPlayer = new MidiPlayer();

            //
            // Options in midiPlayer.Run:
            // 1)  Run()   -- All songs on SD card will play once.
            // 2)  Run(repeat: true)    -- All songs on SD card will play, and repeat at end.
            // 3)  Run("song name")    -- Plays specified song ONCE.
            // 4)  Run("song name", true)   --- Repeats specified song.
            //

            var songThread = new Thread(() => midiPlayer.Run()); //  -- All songs on SD card will play, and DO NOT repeat at end.
            //var songThread = new Thread(() => midiPlayer.Run("elise_format0.mid"));   // example to play single song on SD card,  repeat OFF //
            //

            songThread.Start();

            //Thread.Sleep(Timeout.Infinite);
        }
    }

    public class MidiPlayer
    {
        #region declarations
        //
        public static bool debugMode = false; // Set to true to show all debug.print messages //////////////////////////////////////////
        //
        Cpu.PWMChannel pwm1 = PWMChannels.PWM_PIN_D11; //   Setup pin #'s for PWM channels
        Cpu.PWMChannel pwm2 = PWMChannels.PWM_PIN_D10; // 
        Cpu.PWMChannel pwm3 = PWMChannels.PWM_PIN_D9; // 
        Cpu.PWMChannel pwm4 = PWMChannels.PWM_PIN_D6; //
        Cpu.PWMChannel pwm5 = PWMChannels.PWM_PIN_D5; //
        Cpu.PWMChannel pwm6 = PWMChannels.PWM_PIN_D3; //

        // array of PWMs for channels
        PWM[] speakerChan = new PWM[7]; // Added extra channel for more readable code, Numbers match up //

        const byte maxNumOfPWMpins = 0x06; // Number of simultaneous Outputs, MAXIMUM of 0x06 !!!
        //
        const byte maxNumOfChanToRead = 0x06; // Number of MIDI channels to read from, above this will be ignored.  EX:  0x02 will read events from 0x90 - 0x92, or the first THREE CHANNELS ////
        // If you are getting a lot of "NO AVAILABLE CHANNELS OPEN TO PLAY" messages, you will want to limit it using this method.      ////////////////////////////////////////////////////////
        // Please note that:  EACH CHANNEL may have multiple notes (polyphonic) going on at the same time.   So a busy single channel may also be an issue, since we are limited to 6 PWMS ////
        // Typically of value of 0x02 works nicely.   DO NOT USE OVER 0x08, since channel 0x89/0x99 is percussion/drums and does not translate well to TONES :-) /////////////////////////////
        //
        // 
        // adding extra to each array range, so that Channel numbers MATCH:
        bool[] ChanOn = new bool[maxNumOfPWMpins + 1]; // indicates if channel is ON (playing) and in-use.
        byte[] ChanNote = new byte[maxNumOfPWMpins + 1]; // holds uint note # for each channel
        byte[] ChanVol = new byte[maxNumOfPWMpins + 1]; // volume for each channel
        //
        bool[] updatedNote = new bool[maxNumOfPWMpins + 1]; // flagged when note has changed, so as not to refresh when NOT needed...
        static public bool buttonSkip = false;
        //
        // Sd card:
        const String root = "\\SD\\";
        //
        static public byte[] songString; // store bytes from MIDI file //
        //
        static public System.Collections.Hashtable songsTable = new System.Collections.Hashtable();
        //
        // Direct access to ethernet port reset and Power Led control: //
        OutputPort ethernetPort = new OutputPort((Cpu.Pin)50, true); //0x32 hex
        OutputPort powerLED = new OutputPort((Cpu.Pin)45, true); //0x2f hex
        OutputPort onBoardLED = new OutputPort(Pins.ONBOARD_LED, false);
        //
        //
        #endregion

        public void Run(string songName = "play all songs on card", bool repeat = false) // set with default options
        {
            #region RUN declarations and Initial Setup
            //
            speakerChan[1] = new PWM(pwm1, 100, 0, false); // 6 channels for PWM to play separate notes:
            speakerChan[2] = new PWM(pwm2, 100, 0, false);
            speakerChan[3] = new PWM(pwm3, 100, 0, false);
            speakerChan[4] = new PWM(pwm4, 100, 0, false);
            speakerChan[5] = new PWM(pwm5, 100, 0, false);
            speakerChan[6] = new PWM(pwm6, 100, 0, false);
            //
            int songType;
            int numTracksFoundInSong;
            int numOfBytes;
            int currentByteReadPosition = 0;
            int songTrackBytesLength = 0;
            int startByteNumOfTrack = 0;
            int requestedTicksPerBeat = 140; // rate requested by song //
            int currentSongNumber = 0;
            int sleepyTime = 0;
            //
            double ticksTimeDelay = 0;
            //
            long startTime = 0;
            long stopTime = 0;
            long timeDifference = 0;
            //
            bool foundOn;
            bool foundOff;
            bool foundTrackEnd;
            bool foundTicksDelay;
            bool headerDecodeComplete;
            bool doneWithListOfSongs;
            //
            byte[] tempBytes = new byte[4] { 0x00, 0x00, 0x00, 0x00 };
            byte currentEvent = 0x00;  // current event stored, if "Running Status" is encountered,  event is repeated... //
            byte lastEvent;
            byte currentNote;
            byte currentVolume;
            byte BPM = 120; // default, if not specified.  Equal to 500,000 microseconds per quarter-note            
            byte matchedChannel = 0; // for channel OFF,  find channel that matches the note number to be turned OFF
            //
            // This will be set by each group of On or Off.   Basically time to elapse between events
            uint ticksToCountTo = 24; // init
            //  
            // Button used to SKIP SONGS:
            InterruptPort button = new InterruptPort(Pins.GPIO_PIN_D13, true, Port.ResistorMode.PullDown, Port.InterruptMode.InterruptEdgeBoth);
            button.OnInterrupt += new NativeEventHandler(button_OnInterrupt);
            //
            #endregion

            //
            try
            {
                ethernetPort.Write(false); // saving power...   These are OPTIONAL /////
                powerLED.Write(false);

                doneWithListOfSongs = false; // flagged true when done with single requested song has played,  or when list of files on SD-card have all played ////

                if (songName == "play all songs on card")
                {
                    searchSDForMidiFiles(); // read all MIDI files at the root of the SD card //
                }

                while (!doneWithListOfSongs)
                {
                    #region Check for option to play all songs on SD card
                    //
                    if (songName == "play all songs on card")
                    {
                        readSdCard((string)songsTable[currentSongNumber]); // read current name of song in hash table //
                    }
                    else
                    {
                        readSdCard(songName); // or if single song play mode:  read songname provided //
                    }
                    //
                    numOfBytes = songString.Length; // length (in bytes) of current song
                    buttonSkip = false; // reset
                    //
                    #endregion
                    
                    #region decode header info
                    //
                    Debug.Print("\r\nBeginning song decode process...  (total number of song bytes: " + numOfBytes + ")\r\n");
                    //
                    headerDecodeComplete = false;
                    //
                    if (songString[7] == 0x06) // standard header found:
                    {
                        try
                        {
                            #region Find Song Type and Ticks per BEAT
                            //
                            songType = int.Parse(songString[9].ToString());
                            Debug.Print("New Song Type found: " + songType.ToString());
                            switch (songType)
                            {
                                case 0:
                                    Debug.Print("0: single-track");
                                    break;
                                case 1:
                                    Debug.Print("1: multiple tracks, synchronous (partially supported)");
                                    Debug.Print("Please convert to type 0 for better results...");
                                    Debug.Print("Midi type converter, such as: http://web.archive.org/web/20071011001724/http://www.borg.com/~jglatt/progs/software.htm");
                                    break;
                                case 2:
                                    Debug.Print("2: multiple tracks, asynchronous (NOT supported at all !!!)");
                                    Debug.Print("Please convert to type 0...");
                                    Debug.Print("Midi type converter, such as: http://web.archive.org/web/20071011001724/http://www.borg.com/~jglatt/progs/software.htm");
                                    break;
                            }

                            numTracksFoundInSong = int.Parse(songString[11].ToString());
                            Debug.Print("Number of tracks found in song: " + numTracksFoundInSong.ToString());

                            tempBytes = new byte[4] { 0x00, 0x00, songString[12], songString[13] };
                            try
                            {
                                //Debug.Print("Requested ticks per beat:  " + VariableLengthDecode.BytesToInt32(tempBytes));
                                requestedTicksPerBeat = VariableLengthDecode.BytesToInt32(tempBytes);
                                if (requestedTicksPerBeat == 0)
                                {
                                    Debug.Print("ERROR reading ticks per beat,  defaulting to 140");
                                    Thread.Sleep(2000);
                                    requestedTicksPerBeat = 140;
                                }
                            }
                            catch
                            {
                                Debug.Print("Unable to parse variable length # for Ticks per 1/4 beat !!!");
                                Debug.Print("Defaulting to 140");
                                requestedTicksPerBeat = 140;
                            }
                            //
                            Debug.Print("Number of delta ticks per quarter note: " + requestedTicksPerBeat.ToString());



                            currentByteReadPosition = 14; // move to first position after standard header
                            //
                            #endregion

                            #region decode HEADER BPM and find start of TRACK
                            //
                            while (!headerDecodeComplete)
                            {
                                switch (songString[currentByteReadPosition]) // check current position to see if its an ON / OFF note COMMAND:
                                {
                                    case 0xFF: // System message //
                                        Debug.Print("Found 0xFF in header...");
                                        //
                                        switch (songString[currentByteReadPosition + 1]) // check out one byte position for BPM flag
                                        {
                                            case 0x51: // BPM set //
                                                tempBytes = new byte[4] { 0x00, songString[currentByteReadPosition + 3], songString[currentByteReadPosition + 4], songString[currentByteReadPosition + 5] };
                                                try
                                                {
                                                    //Debug.Print("Variable length, BPM: " + ((UInt32)60000000 / VariableLengthDecode.BytesToInt32(tempBytes)));
                                                    BPM = (byte)((UInt32)60000000 / VariableLengthDecode.BytesToInt32(tempBytes)); // calculate BPM,  bytes read = microseconds //
                                                }
                                                catch // should really NOT be over 255... but, just in case
                                                {
                                                    Debug.Print("Unable to calculate BPM from bytes....  !!!!");
                                                    BPM = 120; // default
                                                }
                                                //
                                                Debug.Print("Found BPM: " + BPM.ToString());
                                                //
                                                currentByteReadPosition += 6;
                                                //
                                                break;
                                            case 0x01:
                                                break; // IGNORE.. for now /////////////
                                        }
                                        break;
                                }

                                // Look for "MTrk" or Start of Track marker:
                                //
                                if (songString[currentByteReadPosition] == 0x4D && songString[currentByteReadPosition + 1] == 0x54 && songString[currentByteReadPosition + 2] == 0x72 && songString[currentByteReadPosition + 3] == 0x6B)
                                {
                                    // Found Track start position !!!!
                                    headerDecodeComplete = true;
                                    //
                                    currentByteReadPosition += 4; // advance 4 bytes to read value of song bytes length
                                    //
                                    // Next four bytes = song track bytes length //
                                    songTrackBytesLength = VariableLengthDecode.fourBytesToInt32(songString[currentByteReadPosition], songString[currentByteReadPosition + 1], songString[currentByteReadPosition + 2], songString[currentByteReadPosition + 3]);
                                    //
                                    Debug.Print("Track bytes length: " + songTrackBytesLength);
                                    //
                                    currentByteReadPosition += 5; // advance 4 bytes to after value + 1 for 0x00 ticks delay //
                                    //
                                    startByteNumOfTrack = currentByteReadPosition; // mark off beginning byte # to be able to count to end position //
                                    //
                                    break;
                                }
                                //
                                currentByteReadPosition++; // NEXT !!!! //                        
                            }
                            //
                            #endregion

                        }
                        catch
                        {
                            Debug.Print("Decode header FAILED !!!");
                            Thread.Sleep(5000);
                        }
                    }
                    //
                    #endregion

                    Debug.GC(true);
                    Debug.Print("\r\nFree memory: " + Debug.GC(true).ToString() + "\r\n");

                    #region find First NOTE ON:
                    //
                    ///// Find first NOTE ON:   ////   Set TEMPO, if FOUND ///   Ignore all other "JUNK"
                    bool foundFirstNote = false;
                    //
                    while (!foundFirstNote)
                    {
                        if (songString[currentByteReadPosition] == 0xFF && songString[currentByteReadPosition + 1] == 0x51) // FF  EVENT // META event //  set TEMPO //
                        {
                            // BPM set //

                            tempBytes = new byte[4] { 0x00, songString[currentByteReadPosition + 3], songString[currentByteReadPosition + 4], songString[currentByteReadPosition + 5] };
                            try
                            {
                                BPM = (byte)((UInt32)60000000 / VariableLengthDecode.BytesToInt32(tempBytes)); // calculate BPM,  bytes read = microseconds //
                            }
                            catch // 
                            {
                                Debug.Print("Unable to calculate BPM from variable length bytes....  !!!!");
                                BPM = 120; // default
                            }
                            //
                            Debug.Print("Found BPM: " + BPM.ToString());
                            //
                            currentByteReadPosition += 6;
                            //
                        }


                        //Debug.Print("scanning for first note on...   position # " + currentByteReadPosition);
                        if (songString[currentByteReadPosition] >= 0x90 && songString[currentByteReadPosition] <= 0x9F)
                        {
                            foundFirstNote = true;
                            printDebug("Found first note !!!  " + songString[currentByteReadPosition]);
                            printDebug("next byte:  " + songString[currentByteReadPosition + 1] + "\r\n");
                            break;
                        }
                        currentByteReadPosition++; // next
                        //
                    }
                    #endregion

                    foundTrackEnd = false; // reset,  this is flagged true when track end bytes are found //
                    lastEvent = 0x00; // init
                    
                    #region Begin Playing Song
                    //
                    // Begin reading main track of song
                    // UNTIL end of track found, or position is at the number of bytes called out
                    //
                    //
                    Debug.Print("\r\nPlaying Song... \r\n");
                    //
                    //
                    // Set delay time for each "TICK":
                    ////////
                    ticksTimeDelay = ((double)60000 / ((double)requestedTicksPerBeat * (double)BPM)); // set initial delay time.   Multiplied by # of ticks after each event. /////
                    Debug.Print("Single Tick time delay (mS): " + ticksTimeDelay);
                    if (ticksTimeDelay == 0)
                    {
                        Debug.Print("\r\nWarning !! Ticks time delay calulated at 0,  defaulting to 1ms !!!\r\n");
                        ticksTimeDelay = 1;
                    }
                    //
                    #endregion
                    
                    //
                    while ((currentByteReadPosition - startByteNumOfTrack) < songTrackBytesLength && !foundTrackEnd) // Play until Track-End flag found,  or run out of "track bytes" //
                    {

                        startTime = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Milliseconds; // measure time taken by code, to compensate later...

                        #region initial Setup:
                        //
                        foundOff = false; // Used as a flag when an ON byte is found
                        foundOn = false; // Used as a flag when an OFF byte is found
                        foundTicksDelay = false; // Indicate that a proper event was found and decode,  meaning its OK to try and decode the Ticks-Delay (pause in-between events) /////
                        //
                        currentNote = 0;  // temp storage for current NOTE
                        currentVolume = 0;  // temp storage for current Volume
                        //
                        for (int i = 1; i < (maxNumOfPWMpins + 1); i++)
                        {
                            updatedNote[i] = false; // Reset Updated NOTE flag.   This is set TRUE when a new note is set to be TURNED ON. //
                        }
                        //
                        ///////////////////////////////

                        printDebug("Current byte position: " + currentByteReadPosition.ToString());
                        //
                        if ((numOfBytes - currentByteReadPosition) > 3) // so as NOT to error at END of SONG: //
                        {
                            printDebug("Current string segment: " + songString[currentByteReadPosition] + ", " + songString[currentByteReadPosition + 1] + ", " + songString[currentByteReadPosition + 2] + ", " + songString[currentByteReadPosition + 3]);
                            //
                        }
                        ////////////////////////////////

                        currentEvent = songString[currentByteReadPosition];  // store current EVENT byte to be processed next: 
                        //
                        #endregion

                        #region decode Current Event type:
                        //
                        if (currentEvent < 128)
                        {
                            // RUNNING STATUS !!!  ///  keep SAME EVENT AS the LAST  (this is used as a method to save data space...)
                            // A standard event will be decimal 128 (hex 0x80) or higher //
                            //
                            currentEvent = lastEvent;
                            currentByteReadPosition--; // backup one position // to compensate for the "missing" event (same as last) //
                            printDebug("Runnnnnnnnnninnnnnnng status...   Last event: " + lastEvent);
                        }

                        //
                        if ((numOfBytes - currentByteReadPosition) > 3) // so as NOT to error at END of SONG: //
                        {
                            if (songString[currentByteReadPosition + 3] > 127) // If MSB is set (ie 128 decimal (hex 0x80) or higher), this flags data as VARIABLE LENGTH.  Will be decoded later ///////
                            {
                                printDebug("Variable length ticks delay: " + songString[currentByteReadPosition + 3] + "," + songString[currentByteReadPosition + 4]);
                            }
                        }

                        //
                        if (currentEvent >= 0x80 && currentEvent <= 0x8F) // CHANNEL OFF//
                        {
                            foundOff = true;
                            foundTicksDelay = true;  //
                            //
                            // EXCLUDE CHANNELS:
                            if (currentEvent > (0x80 + maxNumOfChanToRead)) // can be changed UP to 0x88 (0x89 is drums/percussion)   *************************************
                            {
                                foundOff = false;
                                currentByteReadPosition += 3;
                                printDebug("Excluded Channel OFF message....");
                            }
                        }
                        else if (currentEvent >= 0x90 && currentEvent <= 0x9F) // CHANNEL ON //
                        {
                            foundOn = true;
                            foundTicksDelay = true;  //
                            //
                            // EXCLUDE CHANNELS:
                            if (currentEvent > (0x90 + maxNumOfChanToRead)) // can be changed UP to 0x98 (0x99 is drums/percussion)   *************************************
                            {
                                foundOn = false;
                                currentByteReadPosition += 3;
                                printDebug("Excluded Channel ON message....");
                            }
                        }
                        else if (currentEvent >= 0xA0 && currentEvent <= 0xBF) // Ax (Polyphonic Pressure) or Bx (Controller) EVENT // value, then two bytes
                        {
                            printDebug("IGNORING Ax or Bx type Event.");
                            //
                            currentByteReadPosition += 3;  // skip Ax or Bx value and next TWO bytes:
                            //
                            foundTicksDelay = true;  // next value is ticks delay
                        }
                        else if (currentEvent >= 0xC0 && currentEvent <= 0xDF) // Cx (Program Change) or Dx (Channel Pressure) EVENT //  value, then one byte
                        {
                            printDebug("IGNORING Cx or Dx type Event.");
                            //
                            currentByteReadPosition += 2;  // skip Cx or Dx value and next byte:
                            //
                            foundTicksDelay = true;  // next value is ticks delay
                        }
                        else if (currentEvent >= 0xE0 && currentEvent <= 0xEF) // Ex EVENT (Pitch Bend)// value, then two bytes
                        {
                            printDebug("IGNORING Ex type Event.");
                            //
                            currentByteReadPosition += 3;  // skip Ex value and next TWO bytes:
                            //
                            foundTicksDelay = true;  // next value is ticks delay
                        }
                        else if (currentEvent == 0xFF) // FF  EVENT // META event
                        {
                            printDebug("Found FF type Event.");
                            //
                            if (songString[currentByteReadPosition + 1] == 0x2F && songString[currentByteReadPosition + 2] == 0x00) // found END OF TRACK !!!  // "FF 2F 00"
                            {
                                foundTrackEnd = true;
                                Debug.Print("\r\nFound TRACK END !!!!");
                                break;
                            }
                            else if (songString[currentByteReadPosition + 1] == 0x51) //  set TEMPO //
                            {
                                // BPM set //

                                tempBytes = new byte[4] { 0x00, songString[currentByteReadPosition + 3], songString[currentByteReadPosition + 4], songString[currentByteReadPosition + 5] };
                                try
                                {
                                    BPM = (byte)((UInt32)60000000 / VariableLengthDecode.BytesToInt32(tempBytes)); // calculate BPM,  bytes read = microseconds //
                                }
                                catch // 
                                {
                                    Debug.Print("Unable to calculate BPM from variable length bytes....  !!!!");
                                    BPM = 120; // default
                                }
                                //
                                printDebug("Found BPM: " + BPM.ToString());
                                //
                                ticksTimeDelay = ((double)60000 / ((double)requestedTicksPerBeat * (double)BPM)); // set initial delay time.   Multiplied by # of ticks after each event. /////
                                printDebug("Ticks time delay (mS): " + ticksTimeDelay);
                                if (ticksTimeDelay == 0)
                                {
                                    Debug.Print("\r\nWarning !! Ticks time delay calulated at 0,  defaulting to 1ms !!!\r\n");
                                    ticksTimeDelay = 1;
                                }
                                //
                                currentByteReadPosition += 6;  // skip 6 positions ahead ... to find TICKS delay
                                //
                                foundTicksDelay = true;  // next value is ticks delay
                            }
                            else
                            {
                                printDebug("IGNORING event type FF, sub-type of " + songString[currentByteReadPosition + 1]);
                                //
                                currentByteReadPosition++; // skip next byte which is a META event TYPE // NONE are used here... yet, at least
                                currentByteReadPosition++;
                                //
                                //  Next byte is length of data,  read this and skip ahead that number of bytes +1 to get past current byte /////
                                currentByteReadPosition += songString[currentByteReadPosition] + 1; // 
                                //
                                foundTicksDelay = true;  // next value is ticks delay
                            }
                        }
                        else if (currentEvent == 0xF0 || currentEvent <= 0xF7) // F0 or F7  EVENT // SysEx event
                        {
                            printDebug("IGNORING event type F0 / F7");
                            //
                            currentByteReadPosition++;
                            //
                            //  Next byte is length of data,  read this and skip ahead that number of bytes +1 to get past current byte /////
                            currentByteReadPosition += songString[currentByteReadPosition] + 1; // 
                            //                        
                            foundTicksDelay = true;  // next value is ticks delay
                        }
                        //
                        #endregion

                        #region found ON or OFF
                        //
                        if (foundOff | foundOn)
                        {
                            //Debug.Print("Current string segment: " + songString[currentByteReadPosition] + ", " + songString[currentByteReadPosition + 1] + ", " + songString[currentByteReadPosition + 2] + ", " + songString[currentByteReadPosition + 3]);
                            //
                            currentNote = songString[currentByteReadPosition + 1]; // NOTE
                            currentVolume = songString[currentByteReadPosition + 2]; // VOLUME
                            currentByteReadPosition += 3;  // advance 3 positions //

                            //

                            if (foundOff | (foundOn && currentVolume == 0))  // OFF,   or ON with ZERO VOLUME //
                            {
                                matchedChannel = matchToChan(currentNote); // look for channel with SAME note number
                                printDebug("Matched channel to turn OFF: " + matchedChannel.ToString());
                                if (matchedChannel != 0)
                                {
                                    ChanOn[matchedChannel] = false;
                                    ChanNote[matchedChannel] = currentNote;
                                    ChanVol[matchedChannel] = currentVolume;
                                    updatedNote[matchedChannel] = false;
                                }
                                else
                                {
                                    printDebug("No match on channels to turn off................");
                                }
                            }
                            else if (foundOn && currentVolume > 0)  //  ON  //
                            {
                                byte newChan = findAvailChan(); // find 1st available channel, ie: off, or NOT PLAYING...     0 indicates NO free channels
                                printDebug("New channel open: " + newChan.ToString());
                                if (newChan != 0 && matchToChan(currentNote) == 0)  // check if there IS an available channel, and make sure the current note isn't already playing. //////
                                {
                                    ChanOn[newChan] = true; // TURN ON channel
                                    ChanNote[newChan] = currentNote;  // Set to current NOTE value
                                    ChanVol[newChan] = currentVolume; // Set to current NOTE volume //  which really doesn't matter....  here ///////
                                    updatedNote[newChan] = true; // only "START" a new PWM if channel is updated to PLAY, else keep it the same.
                                    printDebug("Note:  " + ChanNote[newChan] + "  Volume:  " + ChanVol[newChan]);
                                }
                                else  // UH OH...   NO available channels.    You may want to limit the number of channels read, if this happens a LOT in a song.... ///////////
                                {
                                    if (newChan != 0)
                                    {
                                        printDebug("No available channels to PLAY on................");
                                    }
                                    else
                                    {
                                        printDebug("Duplicate note IGNORED...");
                                    }
                                }

                            }
                        }
                        //
                        #endregion

                        #region TICKS delay
                        //
                        ticksToCountTo = 0;
                        //
                        if (foundTicksDelay)
                        {
                            printDebug("Reading ticks delay...");
                            //
                            ticksToCountTo = songString[currentByteReadPosition]; // Ticks to count to
                            if (ticksToCountTo > 127)  /// Variable Length Number ///
                            {
                                ticksToCountTo = VariableLengthDecode.TwoByteDecodeToUInt32(songString[currentByteReadPosition], songString[currentByteReadPosition + 1]);                                
                                currentByteReadPosition++; // advance extra position //
                            }
                            printDebug("Ticks delay: " + ticksToCountTo);
                        }
                        //
                        #endregion

                        #region status check and update PWM's
                        //
                        //status check:
                        //
                        printDebug("Updating PWMS...");
                        //
                        for (uint i = 1; i < (maxNumOfPWMpins + 1); i++)
                        {
                            printDebug("Channel " + i + ", Channel ON? " + ChanOn[i]);                            
                            if (ChanOn[i] && updatedNote[i])
                            {
                                printDebug("Playing NEW note # " + ChanNote[i] + ",  FREQ: " + NoteToFreq(ChanNote[i]));
                                speakerChan[i].Frequency = NoteToFreq(ChanNote[i]);
                                speakerChan[i].DutyCycle = 0.5;
                                speakerChan[i].Start();
                                if (i == 1) // turn ON onboard LED with channel 1 //
                                {
                                    onBoardLED.Write(true);
                                }

                            }
                            else if (!ChanOn[i])
                            {
                                speakerChan[i].Stop();
                                if (i == 1) // turn OFF onboard LED with channel 1 //
                                {
                                    onBoardLED.Write(false);
                                }
                            }

                            printDebug("Channel Note # " + ChanNote[i] + ", Channel Vol " + ChanVol[i]);

                        }
                        printDebug("***** Ticks to count to " + ticksToCountTo + " *****\r\n");
                        //
                        #endregion

                        #region "pause"  TICKS - DELAY ...
                        //
                        if (ticksToCountTo != 0)
                        {                            
                            stopTime = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Milliseconds;

                            timeDifference = stopTime - startTime;
                            
                            if (timeDifference < 0) // negative value means a second went by...
                            {
                                timeDifference = 1000 + timeDifference; // convert to difference.  (Ex:  -991 turns back to the real value of 9mS.)
                            }

                            sleepyTime = (int)(ticksToCountTo * ticksTimeDelay) - (int)timeDifference;

                            printDebug("sleep time: " + sleepyTime);

                            if (sleepyTime > 0)
                            {
                                Thread.Sleep(sleepyTime);
                            }
                        }
                        //
                        #endregion

                        #region Found Track End or Skip-Button Pressed ???
                        //
                        if (!foundTrackEnd) // don't advance position if AT THE END... //////                   
                        {
                            if ((currentByteReadPosition - startByteNumOfTrack) == songTrackBytesLength)  // at the last byte
                            {
                                foundTrackEnd = true;
                            }
                            else
                            {
                                if (currentEvent < 240)  // ALSO,  if current event is F0 - FF,  DO NOT save it as last event for running status use //
                                {
                                    lastEvent = currentEvent;
                                }
                                //
                                currentByteReadPosition++; // next  /////
                            }
                        }


                        if (buttonSkip)
                        {
                            foundTrackEnd = true;
                        }
                        //
                        #endregion

                    }

                    #region Song Complete
                    //
                    Debug.Print("Ensuring all PWM's are off...");

                    //  turn OFF all sounds
                    for (uint i = 1; i < (maxNumOfPWMpins + 1); i++)
                    {
                        speakerChan[i].Stop();
                        ChanOn[i] = false; // necessary if playing another song after this // RESET //
                    }

                    Debug.Print("Song Complete !!!");


                    Thread.Sleep(1000);  //


                    if (songName == "play all songs on card")
                    {
                        if (currentSongNumber < (songsTable.Count - 1))
                        {
                            currentSongNumber++; // advance to next song //
                        }
                        else if (currentSongNumber == (songsTable.Count - 1) && !repeat)
                        {
                            doneWithListOfSongs = true; // or at last song, with repeat  OFF == DONE.
                        }
                        else
                        {
                            currentSongNumber = 0;   //   reset song list and REPEAT must be true;
                        }
                    }
                    else if (!repeat)
                    {
                        doneWithListOfSongs = true;  // if REPEAT is true,  leave FALSE.
                    }
                    //
                    //
                    #endregion
                }

            }
            catch (Exception ex)
            {
                Debug.Print(" !!!!!!!!!!!!!!   ERROR    !!!!!!!!!!!!!!!! ");
                //
                Debug.Print(ex.Message);
            }
        }

        void button_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            if (!buttonSkip)
            {
                buttonSkip = true;
                Debug.Print("\r\nSkipping song... \r\n");
            }
        }

        void printDebug(string message)
        {
            if (debugMode)
            {
                Debug.Print(message);
            }
        }

        void readSdCard(string fileName)
        {
            Debug.Print("\r\nAttempting SD card read: " + fileName);

            try
            {
                Directory.SetCurrentDirectory(root);

                if (System.IO.File.Exists(fileName))
                {
                    songString = File.ReadAllBytes(fileName);
                    Debug.Print("Read complete.");
                }
                else
                {
                    Debug.Print("File not found...");
                }
            }
            catch (Exception ex)
            {
                Debug.Print(ex.Message);
            }
        }

        void searchSDForMidiFiles()
        {
            string temp = "";
            DirectoryInfo rootDirectory = new DirectoryInfo(root);
            int i = 0;

            Debug.Print("\r\nList of MIDI files on SD card: \r\n");

            if (rootDirectory.Exists)
            {
                foreach (FileInfo file in rootDirectory.GetFiles())
                {
                    if (file.Extension == ".mid")
                    {
                        temp = file.FullName.Substring(root.Length); // remove root info at beginning, i.e. "\SD\", etc...
                        songsTable.Add(i, temp);
                        Debug.Print(file.FullName);
                        i++;
                    }
                }
            }
        }

        uint NoteToFreq(byte noteIn)
        {
            // thanks to:  http://subsynth.sourceforge.net/midinote2freq.html /////
            double a = 440; // standard tuning //
            double freq = (a / (double)32) * (System.Math.Pow(2, (double)((double)(noteIn - 9) / 12)));
            if (freq < 16)
            {
                freq = 16;
            }
            return (uint)freq;
        }

        byte findAvailChan()
        {
            byte availChan = 0;
            for (byte i = 1; i < (maxNumOfPWMpins + 1); i++)
            {
                if (!ChanOn[i])
                {
                    availChan = i;
                    break;
                }
            }

            return availChan; // returns 0 if NO available channels //
        }

        byte matchToChan(byte noteNum)
        {
            byte chanNumToReturn = 0;
            for (byte i = 1; i < (maxNumOfPWMpins + 1); i++) // scan all channels to find matching note-Number
            {
                if (ChanNote[i] == noteNum && ChanOn[i])
                {
                    chanNumToReturn = i;
                }
            }
            return chanNumToReturn; // if 0,  then NO match was found //
        }
    }

    public class VariableLengthDecode
    {
        // also reference:  http://home.roadrunner.com/~jgglatt/tech/midifile/vari.htm  for info //

        public static byte[] ToBytes(byte[] variableDataInput)
        {
            int offset = 1;
            UInt32 value = variableDataInput[0];
            UInt32 numBytes = 1;

            if (((value & 0x80) != 0))
            {
                UInt32 c;
                value &= 0x7F;
                do
                {
                    c = variableDataInput[offset];
                    value = (value << 7) + (c & 0x7F);
                    ++numBytes;
                    ++offset;
                } while ((c & 0x80) != 0);
            }

            return UintToBytes(value);
        }

        public static UInt32 ToUInt32(byte[] variableDataInput)
        {
            // thanks to:  http://www.lastrayofhope.com/tag/midi/ //
            //
            int offset = 1;
            UInt32 value = variableDataInput[0];
            UInt32 numBytes = 1;

            if (((value & 0x80) != 0))
            {
                UInt32 c;
                value &= 0x7F;
                do
                {
                    c = variableDataInput[offset];
                    value = (value << 7) + (c & 0x7F);
                    ++numBytes;
                    ++offset;
                } while ((c & 0x80) != 0);
            }

            Debug.Print(value.ToString());

            return value;
        }

        public static UInt32 TwoByteDecodeToUInt32(byte variableDataPositionONE, byte variableDataPositionTWO)
        {
            // thanks to:  http://www.lastrayofhope.com/tag/midi/ // for original code.   This is modified, taylored by my use //
            //
            byte[] variableDataInput = new byte[2] { 
                //(byte)(0x00),    /// MSB
                //(byte)(0x00),
                (byte)(variableDataPositionONE),
                (byte)(variableDataPositionTWO)   /// LSB
            };

            //Debug.Print("Input hex: " + debugBytes2string(variableDataInput));

            int offset = 1;
            UInt32 value = variableDataInput[0];
            UInt32 numBytes = 1;

            if (((value & 0x80) != 0))
            {
                UInt32 c;
                value &= 0x7F;
                do
                {
                    c = variableDataInput[offset];
                    value = (value << 7) + (c & 0x7F);
                    ++numBytes;
                    ++offset;
                } while ((c & 0x80) != 0);
            }

            //Debug.Print("Shifted result: " + debugBytes2string(UintToBytes(value)));

            return value;
        }

        public static byte[] UintToBytes(uint value)
        {
            return new byte[4] {    ////  reversed for correct order ////
                (byte)((value >> 24) & 0xFF),
                (byte)((value >> 16) & 0xFF),
                (byte)((value >> 8) & 0xFF),
                (byte)(value & 0xFF) };
        }

        public static int BytesToInt32(byte[] value)
        {
            return (
                value[0] << 24 |
                value[1] << 16 |
                value[2] << 8 |
                value[3] << 0);
        }

        public static int fourBytesToInt32(byte value1, byte value2, byte value3, byte value4)
        {
            return (
                value1 << 24 |
                value2 << 16 |
                value3 << 8 |
                value4 << 0);
        }

        public static string debugBytes2string(byte[] tempByte)
        {
            string temp = "";
            for (int b = 0; b < tempByte.Length; b++)
            {
                temp += (tempByte[b].ToString());
                if (b != tempByte.Length - 1)
                {
                    temp += ",";
                }
            }
            return temp;
        }
    }
    //
}
